package crew.gwt.client.widgets.util;

import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;

import com.google.gwt.core.client.GWT;
import com.google.gwt.event.dom.client.ChangeEvent;
import com.google.gwt.event.dom.client.ClickEvent;
import com.google.gwt.event.dom.client.ClickHandler;
import com.google.gwt.event.dom.client.KeyCodes;
import com.google.gwt.event.dom.client.KeyPressEvent;
import com.google.gwt.uibinder.client.UiBinder;
import com.google.gwt.uibinder.client.UiField;
import com.google.gwt.uibinder.client.UiHandler;
import com.google.gwt.uibinder.client.UiTemplate;
import com.google.gwt.user.client.Command;
import com.google.gwt.user.client.ui.Button;
import com.google.gwt.user.client.ui.ComplexPanel;
import com.google.gwt.user.client.ui.Composite;
import com.google.gwt.user.client.ui.FlexTable;
import com.google.gwt.user.client.ui.HorizontalPanel;
import com.google.gwt.user.client.ui.MenuBar;
import com.google.gwt.user.client.ui.MenuItem;
import com.google.gwt.user.client.ui.ToggleButton;
import com.google.gwt.user.client.ui.Widget;

import crew.gwt.client.domain.PersistableEntity;
import crew.gwt.client.widgets.ClassicLabel;
import crew.gwt.client.widgets.CrewListBox;
import crew.gwt.client.widgets.DialogPopupPanel;
import crew.gwt.client.widgets.handler.DialogHandler;
import crew.gwt.client.widgets.handler.OnActionEntityHandler;

public abstract class RichEntityGrid<T extends PersistableEntity<T>> extends Composite implements ClickHandler {

	@UiTemplate("RichEntityGrid.ui.xml")
	interface GdUIBinder extends UiBinder<Widget, RichEntityGrid<?>> {}
	private static GdUIBinder uiBinder = GWT.create(GdUIBinder.class);
	
	@UiField ClassicLabel entityCtLb;
	@UiField FlexTable table;

	//Features
	/**
	 * Used to know on entity event ( add, edit, remove ) which widget had to be called for refresh.
	 */
	private final Map<T,Set<UpdatableEntityWidget<?,T>>> featureWidgetOfEntity = new HashMap<T,Set<UpdatableEntityWidget<?,T>>>();
	/**
	 * Keep reference of all features activated, used to create the UpdatableEntityWidget related of the entities.<br/>
	 * Keep their index for horizontal position too, used to remove the feature in UI
	 */
	private final Map<EntityGridFeature<?,T>,Integer> featureOnColumn = new HashMap<EntityGridFeature<?,T>,Integer>();
	
	//Action
	private OnActionEntityHandler<T> onSelectHandler = null;
	//Sorting
	private ToggleButton ascTbt = new ToggleButton();
	private int sortingPropertyIndex = 1;
	//Paging
	@UiField NumberBox manualPageNumberNb;
	@UiField(provided=true) Button previousPageBt = new Button("<span class='EntityGrid-Paging-Previous' />");
	@UiField(provided=true) Button nextPageBt = new Button("<span class='EntityGrid-Paging-Next' />");
	@UiField CrewListBox capacityLb;
	private int pageNb ;
	private int entitiesNb = 0;

	//Util for index
	private int iTable = 0;
	private Map<Integer, Integer> iTable2iEntity = new HashMap<Integer, Integer>();
	//Entities
	private List<T> entities = new ArrayList<T>();
	
	public RichEntityGrid(final T wildcartType) {
		initWidget(uiBinder.createAndBindUi(this));
		setHeaders(wildcartType);
		capacityLb.addItem("Taille : 5","5");
		capacityLb.addItem("Taille : 10","10");
		capacityLb.addItem("Taille : 20","20");
		capacityLb.addItem("Taille : 50","50");
		capacityLb.addItem("Taille : 100","100");
		capacityLb.setSelectedIndex(1);
		setPageNb(1);

		ascTbt.setStyleName("EntityGrid-Sorting-Dsc", true);
		ascTbt.setTitle("Trier par ordre asc ou desc");
		ascTbt.addClickHandler(new ClickHandler() {
			@Override
			public void onClick(ClickEvent arg0) {
				boolean addAsc = ascTbt.isDown();
				ascTbt.setStyleName("EntityGrid-Sorting-Asc", addAsc);
				ascTbt.setStyleName("EntityGrid-Sorting-Dsc", !addAsc);
			
				sortGrid(sortingPropertyIndex, getAsc(), getStartIndex(), getGridCapacity());
			}
		});
		table.addStyleName("EntityGrid");
	}

	protected void loadEntities(){
		sortGrid(0, getAsc(), getStartIndex(), getGridCapacity());
	}
	
	private void setHeaders(T t) {
		// Button asc/desc
		ComplexPanel p = new HorizontalPanel();
		p.add(ascTbt);
		table.setWidget(0, 0, p);
		
		int i = 1;
		// For each headers of the entity
		for(String header : t.getHeadersValues()){
			final int index = i;
			// Set text, style and handler
			table.getCellFormatter().addStyleName(0, index,"EntityGrid-CellHeader");
			table.setWidget(0, i, new ClassicLabel(header,new ClickHandler() {
				@Override
				public void onClick(ClickEvent arg0) {
					table.getCellFormatter().removeStyleName(0, sortingPropertyIndex+1,"EntityGrid-CellHeader-Sorted");
					table.getCellFormatter().addStyleName(0, index,"EntityGrid-CellHeader-Sorted");
					sortingPropertyIndex = index-1;
					sortGrid(index-1,getAsc(),getStartIndex(),getGridCapacity());
				}}));
			i++;
		}
		table.getRowFormatter().addStyleName(0, "EntityGrid-RowHeader");
	}

	//--------------------------------------- Features
	
	private EntityGridFeatureManager<T> featuresManager = null;
	public EntityGridFeatureManager<T> getEntityGridFeatureManager(){
		if( featuresManager == null ) {
			featuresManager = new EntityGridFeatureManager<T>(this);
			addEntityGridFeature(featuresManager);
		}
		return featuresManager;
	}
	
	public void addEntityGridFeature(EntityGridFeature<?,T> feature){
		((ComplexPanel)table.getWidget(0, 0)).add(feature.getHeader());

		for(int i = 1 ; i <= entities.size(); i++){
			// Find for which entity the widget is
			T entityForWidget = entities.get(i-1); 
			// Add feature to the entity
			addFeatureToEntity(feature,entityForWidget);
		}
		featureOnColumn.put(feature,featureOnColumn.size());
	}

	private void addFeatureToEntity(EntityGridFeature<?,T> feature, T entityForWidget) {

		// This entity is really in our panel ?
		int index = entities.indexOf(entityForWidget);
		if(index > -1){
			
			// Create the UpdatableWidget for this entity
			UpdatableEntityWidget<?,T> entityWidget = feature.getEnityRowWidget(entityForWidget); 
			// Put this one into the list of UpdatableEntityWidget related of this entity
			if( featureWidgetOfEntity.get(entityForWidget) == null )
				featureWidgetOfEntity.put(entityForWidget, new HashSet<UpdatableEntityWidget<?,T>>());
			featureWidgetOfEntity.get(entityForWidget).add(entityWidget);
			
			// UI
			index += 1; // Add the Header row
			((ComplexPanel)table.getWidget(index, 0)).add(entityWidget.getWidget());
			entityWidget.onContextChange();
		}
	}

	public void removeEntityGridFeature(EntityGridFeature<?,T> feature){

		Integer colomunOfFeature = featureOnColumn.get(feature)+1; // Add the Action used column
		assert colomunOfFeature != null : "index null" ; 

		featureOnColumn.remove(feature);

		// Remove all widget related of this feature
		for(UpdatableEntityWidget<?,T> widget : feature.getWidgetOfEntity().values()){
			// For each entity in my grid, remove the widget of this feature from the lists
			for( T entity : entities){
				featureWidgetOfEntity.get(entity).remove(widget);
			}
		}
		
		// UI
		((ComplexPanel)table.getWidget(0, 0)).remove(colomunOfFeature); // Remove feature header
		for(int i = 1 ; i < table.getRowCount(); i++){
			((ComplexPanel)table.getWidget(i, 0)).remove(colomunOfFeature); // Remove feature header
		}
	}
	
	//--------------------------------------- Entities & Index
	
	public void addEntity(T t){
		if(!entities.contains(t)){
			entities.add(t);
			setEntityRow(t,entities.size());
			iTable2iEntity.put( iTable++, entities.size()-1 );
			
			// Feature
			for(EntityGridFeature<?,T> feature : featureOnColumn.keySet())
				addFeatureToEntity(feature, t);
		}
	}
	
	public void addEntities(Collection<T> list){
		for(T t : list)
			addEntity(t);
	}
	
	public void clear() {
		iTable = 0;
		iTable2iEntity.clear();
		entities.clear();
		while(table.getRowCount()!=1)
			table.removeRow(1);
	}
	
	public void cleanIndex(int index) {
		iTable2iEntity.remove(index);
		for(Integer key : iTable2iEntity.keySet()){
			if( key > index) iTable2iEntity.put(key, index++);
		}
	}
	
	public boolean editEntity(T t){
		int index = entities.indexOf(t);
		if(index > -1){
			entities.remove(index);
			entities.add(index, t);
			editEntityRow(t,index+1);

			// Feature
			if( !featureWidgetOfEntity.isEmpty() ){
				for(UpdatableEntityWidget<?,T> widgetToUpdate : featureWidgetOfEntity.get(t)){
					widgetToUpdate.setEntity(t);
					widgetToUpdate.onContextChange();
				}
			}
			return true;
		}
		return false;
	}
	
	public boolean removeEntity(T t){
		int index = entities.indexOf(t);
		if(index > -1){
			entities.remove(index);
			table.removeRow(index+1);
			cleanIndex(index);
			return true;
		}
		return false;
	}
	
	public List<T> getEntities(){
		return entities;
	}
	
	protected void editEntityRow(T t,final int toRowNb){
		final int rowToUse = toRowNb ;
		if( ( rowToUse%2 ) == 0)	
			table.getRowFormatter().addStyleName(rowToUse, "EntityGrid-RowClassic");
		else
			table.getRowFormatter().addStyleName(rowToUse, "EntityGrid-RowAlt");
		
		int i = 1;
		for(String value : t.getValues()){
			table.setWidget(rowToUse, i++, new ClassicLabel(value));
		}
	}
	
	protected void setEntityRow(T t,final int toRowNb){
		table.setWidget(toRowNb , 0	, createActionMenu(iTable));
		editEntityRow(t, toRowNb);
	}

	//--------------------------------------- Handler

	@UiHandler("previousPageBt")
	void onPreviousPageBtClickEvent(ClickEvent e){
		setPageNb(getPageNb()-1);
		sortGrid(sortingPropertyIndex, getAsc(), getStartIndex(), getGridCapacity());
	}
	
	@UiHandler("nextPageBt")
	void onNextPageBtClickEvent(ClickEvent e){
		setPageNb(getPageNb()+1);
		sortGrid(sortingPropertyIndex, getAsc(), getStartIndex(), getGridCapacity());
	}
	
	@UiHandler("capacityLb")
	void onCapacityLbChangeEvent(ChangeEvent e){
		setPageNb(1);
		sortGrid(sortingPropertyIndex, getAsc(), getStartIndex(), getGridCapacity());
	}

	@UiHandler("manualPageNumberNb")
	void onManualPageNumberTbtKeyPressEvent(KeyPressEvent e){
		if( e.getCharCode() == KeyCodes.KEY_ENTER ){
			setPageNb((manualPageNumberNb.getInteger()> getPageCt()) ? getPageCt(): manualPageNumberNb.getInteger());
			sortGrid(sortingPropertyIndex, getAsc(), getStartIndex(), getGridCapacity());
		}
	}

	public void setOnSelectHandler(OnActionEntityHandler<T> onSelectHandler) {
		this.onSelectHandler = onSelectHandler;
	}
	
	@Override
	public void onClick(ClickEvent e) {
		if(onSelectHandler != null){
			Button b = (Button) e.getSource();
			int indexOfEntity = b.getTabIndex()-1;
			if( indexOfEntity >= 0 && indexOfEntity < entities.size() )
				onSelectHandler.onSelectEntity(entities.get(indexOfEntity));
		}
	};
	
	//--------------------------------------- Paging
	
	private int getPageCt(){
		return (int)Math.ceil(((float)getEntitiesNb())/getGridCapacity());
	}
	
	protected int getGridCapacity(){
		try{
			return Integer.valueOf(capacityLb.getSelectedValue());
		}catch (Exception e) {
			return 20;
		}
	}
	
	protected int getStartIndex(){
		return ( getGridCapacity() * getPageNb() ) - getGridCapacity();
	}
	

	protected boolean getAsc(){
		return (!ascTbt.isDown());
	}

	protected abstract void sortGrid(int propertyIndex,boolean asc,int startIndex,int gridCapacity);
	
	//--------------------------------------- Action Menu
	
	private ComplexPanel createActionMenu(int index){
		final MenuBar bar = new MenuBar(true);
		bar.setAnimationEnabled(true);
		bar.setAutoOpen(false);
		bar.setFocusOnHoverEnabled(false);

		final MenuBar actions = new MenuBar(true);
		actions.setAnimationEnabled(true);
		actions.addItem(createActionSelectItem(index));
		actions.addSeparator();
		actions.addItem(createActionEditItem(index));
		actions.addSeparator();
		actions.addItem(createActionDeleteItem(index));
		
		bar.addItem(new MenuItem("<span class='EntityGrid-Action'>Actions</span>",true, actions));
		HorizontalPanel p = new HorizontalPanel();
		p.add(bar);
		return p;
	}
	
	private MenuItem createActionSelectItem(final int iTableEntity){
		return new MenuItem("<span class='EntityGrid-Action-Select'>Utiliser</span>",true,new Command(){
			@Override
			public void execute() {
				if(onSelectHandler != null){
					final Integer iListEntity = iTable2iEntity.get(iTableEntity);
					if( iListEntity != null && iListEntity >= 0 && iListEntity < entities.size() )
						onSelectHandler.onSelectEntity(entities.get(iListEntity));
				}
			}});
	}

	private MenuItem createActionEditItem(final int iTableEntity){
		return new MenuItem("<span class='EntityGrid-Action-Edit'>Editer</span>",true,new Command(){
			@Override
			public void execute() {
				if(onSelectHandler != null){
					final Integer iListEntity = iTable2iEntity.get(iTableEntity);
					if( iListEntity != null && iListEntity >= 0 && iListEntity < entities.size() )
						onSelectHandler.onEditEntity(entities.get(iListEntity));
				}
			}});
	}
	
	private MenuItem createActionDeleteItem(final int iTableEntity){
		return new MenuItem("<span class='EntityGrid-Action-Delete'>Effacer</span>",true,new Command(){
			@Override
			public void execute() {
				if(onSelectHandler != null){

					new DialogPopupPanel(new ClassicLabel("Voulez vous vraiment éffacer l'entrée ?"), new DialogHandler() {
						@Override
						public void onConfirmClick() {
							final Integer iListEntity = iTable2iEntity.get(iTableEntity);
							if( iListEntity != null && iListEntity >= 0 && iListEntity < entities.size() )
								onSelectHandler.onDeleteEntity(entities.get(iListEntity));
						}
						@Override
						public void onCancelClick() {}
					});
				}
			}});
	}
	
	//--------------------------------------- Getters & Setters

	public int getPageNb() {
		return pageNb;
	}
	
	public Map<EntityGridFeature<?,T>,Integer> getFeatureOnColumn() {
		return featureOnColumn;
	}

	public int getEntitiesNb() {
		return entitiesNb;
	}
	
	public void setEntitiesNb(int entitiesNb) {
		this.entitiesNb = entitiesNb;
		entityCtLb.setText("Nombre d'éléments total: "+String.valueOf(entitiesNb));
	}

	public void setPageNb(int pageNb) {
		this.pageNb = pageNb;
		manualPageNumberNb.setText(String.valueOf(pageNb));
	}
}
